package com.j2eefast.compressor.maven;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.Scanner;
import org.sonatype.plexus.build.incremental.BuildContext;

/**
 * Common class for mojos.
 *
 * @author David Bernard 
 * @author J2eeFAST
 * @created 2007-08-29
 */
public abstract class MojoSupport extends AbstractMojo {

    private static final String[] EMPTY_STRING_ARRAY = {};

    /**
     * Javascript source directory. (result will be put to outputDirectory). This allow project with "src/main/js" structure.
     *
     * @ parameter expression="${project.build.sourceDirectory}/../js"
     */
    @Parameter(property = "sourceDirectory", defaultValue = "")
    private File sourceDirectory;

    /**
     * Single directory for extra files to include in the WAR.
     *
     * @ parameter expression="${basedir}/src/main/webapp"
     */
    @Parameter(property = "warSourceDirectory", defaultValue = "${basedir}/src/main/webapp")
    private File warSourceDirectory;
    
    /**
     * 压缩历史文件夹
     */
    @Parameter(property = "tempSourceDirectory", defaultValue = "${basedir}/.temp/")
    protected String tempSourceDirectory;

    /**
     * 未改变不再压缩使用之前的
     */
    @Parameter(property = "uncompressedChange", defaultValue = "true")
    protected boolean uncompressedChange;
    
    
    /**
     * The directory where the webapp is built.
     *
     * @ parameter expression="${project.build.directory}/${project.build.finalName}"
     */
    @Parameter(property = "webappDirectory", defaultValue = "${project.build.directory}/${project.build.finalName}")
    private File webappDirectory;

    /**
     * The output directory into which to copy the resources.
     *
     * @ parameter property="project.build.outputDirectory"
     */
    @Parameter(property = "outputDirectory", defaultValue = "${project.build.outputDirectory}")
    private File outputDirectory;

    /**
     * The list of resources we want to transfer.
     *
     * @ parameter property="project.resources"
     */
    @Parameter(property = "resources", defaultValue = "${project.resources}")
    private List<Resource> resources;

    /**
     * list of additional excludes
     *
     * @ parameter
     */
    @Parameter(property = "excludes")
    private List<String> excludes;

    /**
     * Use processed resources if available
     *
     * @ parameter default-value="false"
     */
    @Parameter(property = "useProcessedResources", defaultValue = "false")
    private boolean useProcessedResources;

    /**
     * list of additional includes
     *
     * @ parameter
     */
    @Parameter(property = "includes")
    private List<String> includes;

    /**
     * Excludes files from webapp directory
     *
     * @ parameter
     */
    @Parameter(property = "excludeWarSourceDirectory")
    private boolean excludeWarSourceDirectory = false;

    /**
     * Excludes files from resources directories.
     *
     * @ parameter default-value="false"
     */
    @Parameter(property = "excludeResources")
    private boolean excludeResources = false;

    /**
     * @ parameter property="project"
     * @readonly
     * @required
     */
    @Parameter(property = "project")
    protected MavenProject project;

    
    /**
     * [js only] Display possible errors in the code
     *
     * @ parameter property="maven.compressor.jswarn" default-value="true"
     */
    @Parameter(property = "jswarn", defaultValue = "true")
    protected boolean jswarn;


    @Parameter(property = "thread", defaultValue = "0")
    protected int thread;

    /**
     * Whether to skip execution.
     *
     * @ parameter property="maven.compressor.skip" default-value="false"
     */
    @Parameter(property = "skip", defaultValue = "false")
    private boolean skip;

    /**
     * define if plugin must stop/fail on warnings.
     *
     * @ parameter property="maven.compressor.failOnWarning" default-value="false"
     */
    @Parameter(property = "failOnWarning", defaultValue = "false")
    protected boolean failOnWarning;

    /** @ component */
    @Component
    protected BuildContext buildContext;

    protected ErrorReporter4Mojo jsErrorReporter_;

    protected Long startTime = 0L;

    protected Long endTime = 0L;
    
    protected File tempDirs = null;
    
    protected JSONObject fileKey = JSONUtil.createObj();

    private List<String> resourcesPath = new ArrayList<>();

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        try {
            if (skip) {
                getLog().info("run of compressor-maven-plugin skipped");
                return;
            }
            if (failOnWarning) {
                jswarn = true;
            }
            jsErrorReporter_ = new ErrorReporter4Mojo(getLog(), jswarn, buildContext);
            
            getLog().info("compress directory(压缩完成历史文件夹):" + this.tempSourceDirectory);
            
            if(uncompressedChange) {
            	//判断创建临时目录
                tempDirs = FileUtil.touch(this.tempSourceDirectory + "temp.json");
                
                if(!FileUtil.isEmpty(tempDirs)) {
                	String src = FileUtil.readString(tempDirs,"UTF-8");
                	fileKey = JSONUtil.parseObj(src);
                }
            }else {
            	try {
            		FileUtil.clean(this.tempSourceDirectory);
            	}catch (Exception e) {
				}
            }
            
            beforeProcess();
            
            if (sourceDirectory != null) {
                getLog().info("Source directory: " + sourceDirectory);
                processDir(sourceDirectory, outputDirectory, null, useProcessedResources);
            }
            if (!excludeResources) {
                for (Resource resource : resources) {
                    File destRoot = outputDirectory;
                    if (resource.getTargetPath() != null) {
                        destRoot = new File(outputDirectory, resource.getTargetPath());
                    }
                    if(!resourcesPath.contains(resource.getDirectory())){
                        getLog().info(String.format("Resources: %s   Excludes: %s" , resource.getDirectory(),
                                StrUtil.join(",",resource.getExcludes().toString())));
                        processDir(new File(resource.getDirectory()), destRoot, resource.getExcludes(), useProcessedResources);
                        resourcesPath.add(resource.getDirectory());
                    }
                }
            }
            if (!excludeWarSourceDirectory) {
                getLog().info("WarSource: " + warSourceDirectory);
                processDir(warSourceDirectory, webappDirectory, null, useProcessedResources);
            }
            afterProcess();
            getLog().info(String.format("nb warnings: %d, nb errors: %d", jsErrorReporter_.getWarningCnt(), jsErrorReporter_.getErrorCnt()));
            getLog().info(String.format("Total time: %.3f sec", (endTime - startTime) * .001));
            if (failOnWarning && (jsErrorReporter_.getWarningCnt() > 0)) {
                throw new MojoFailureException("warnings on " + this.getClass().getSimpleName() + "=> failure ! (see log)");
            }
        } catch (RuntimeException exc) {
            throw exc;
        } catch (MojoFailureException exc) {
            throw exc;
        } catch (MojoExecutionException exc) {
            throw exc;
        } catch (Exception exc) {
            throw new MojoExecutionException("wrap: " + exc.getMessage(), exc);
        }
    }

    protected abstract String[] getDefaultIncludes() throws Exception;

    protected abstract void beforeProcess() throws Exception;

    protected abstract void afterProcess() throws Exception;

    /**
     * Force to use defaultIncludes (ignore srcIncludes) to avoid processing resources/includes from other type than *.css or *.js
     * @see https://github.com/davidB/yuicompressor-maven-plugin/issues/19
     */
    protected void processDir(File srcRoot, File destRoot, List<String> srcExcludes, boolean destAsSource) throws Exception {
        if ((srcRoot == null) || (!srcRoot.exists())) {
            return;
        }
        if (destRoot == null) {
            throw new MojoFailureException("destination directory for " + srcRoot + " is null");
        }
        DirectoryScanner scanner = new DirectoryScanner();
        scanner.setBasedir(srcRoot);
        Scanner incrementalScanner = buildContext.newScanner(srcRoot);

        if (includes == null) {
            scanner.setIncludes(getDefaultIncludes());
            incrementalScanner.setIncludes(getDefaultIncludes());
        } else {
            scanner.setIncludes(includes.toArray(new String[0]));
            incrementalScanner.setIncludes(includes.toArray(new String[0]));
        }

        if ((srcExcludes != null) && !srcExcludes.isEmpty()) {
            scanner.setExcludes(srcExcludes.toArray(EMPTY_STRING_ARRAY));
        }
        if ((excludes != null) && !excludes.isEmpty()) {
            scanner.setExcludes(excludes.toArray(EMPTY_STRING_ARRAY));
        }
        scanner.addDefaultExcludes();
        incrementalScanner.addDefaultExcludes();
        if (buildContext.isIncremental()) {
            incrementalScanner.scan();
            if (incrementalScanner.getIncludedFiles() == null || incrementalScanner.getIncludedFiles().length == 0) {
                getLog().info("No files have changed, so skipping the processing");
                return;
            }
        }
        scanner.scan();
        String[] includedFiles = scanner.getIncludedFiles();
        getLog().info("Scan files number: " + includedFiles.length);

//        CountDownLatch Latch = new CountDownLatch(includedFiles.length);
        for (String name : includedFiles) {
            SourceFile src = new SourceFile(srcRoot, destRoot, name, destAsSource);
            jsErrorReporter_.setDefaultFileName("..." + src.toFile().getAbsolutePath().substring(src.toFile().getAbsolutePath().lastIndexOf('/') + 1));
            jsErrorReporter_.setFile(src.toFile());
            processFile(src);
//            ThreadUtil.execute(()->{
//                try {
//                    getLog().info("线程开始: " + Thread.currentThread().getName());
//                    processFile(src);
//                    Latch.countDown();
//                } catch (Exception e) {
//                    getLog().error(e);
//                }
//            });
        }
//        Latch.await();
    }

    /**
     * 处理文件
     * @param src
     * @throws Exception
     */
    protected abstract void processFile(SourceFile src) throws Exception;
}
